package com.wxzhou.wangzhuan.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wxzhou.wangzhuan.config.ApiSecurityConfig;
import com.wxzhou.wangzhuan.dto.CommonRequestDto;
import com.wxzhou.wangzhuan.dto.ResponseDto;
import com.wxzhou.wangzhuan.util.ApiSecurityUtils;
import io.quarkus.logging.Log;
import io.quarkus.redis.datasource.ReactiveRedisDataSource;
import io.quarkus.redis.datasource.value.ReactiveValueCommands;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import org.jboss.logging.Logger;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneOffset;

@Provider
public class ApiSecurityReaderInterceptor implements ReaderInterceptor {

    private static final Logger LOG = Logger.getLogger(ApiSecurityReaderInterceptor.class);

    @Inject
    ApiSecurityConfig securityConfig;

    @Inject
    ReactiveRedisDataSource redisDataSource;

    @Inject
    ObjectMapper objectMapper;

    @Inject
    ApiSecurityRequestContext securityRequestContext;

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
        // 仅处理 application/json
        MediaType mediaType = context.getMediaType();
        if (mediaType == null || !MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType)) {
            return context.proceed();
        }

        if (!securityConfig.isEnabled()) {
            return context.proceed();
        }

        String path = context.getHeaders().getFirst("x-original-path");
        // Quarkus无法直接从ReaderInterceptor拿到UriInfo，改为从请求头透传，若拿不到则跳过
        if (path == null) {
            return context.proceed();
        }

        // 排除 /api/cache-management/* 路径，由 AdminApiSecurityRequestFilter 处理
        if (isCacheManagementPath(path)) {
            LOG.debug("Skipping cache-management path, handled by AdminApiSecurityRequestFilter path=" + path);
            return context.proceed();
        }

        try {
            byte[] bodyBytes = context.getInputStream().readAllBytes();
            String requestBody = new String(bodyBytes, StandardCharsets.UTF_8);
            LOG.infof("加密请求参数:%s",requestBody);
            if (requestBody == null || requestBody.isEmpty()) {
                abort("不允许空请求，请传{}");
                return null; // unreachable
            }

            CommonRequestDto dto = objectMapper.readValue(requestBody, CommonRequestDto.class);

            // 校验基础字段
            if (dto.getCtime() == null) abort("少字段: ctime");
            if (dto.getSign() == null || dto.getSign().isEmpty()) abort("少字段: sign");
            if (dto.getVersionName() == null || dto.getVersionName().isEmpty()) abort("少字段: versionName");
            if (dto.getVersionCode() == null) abort("少字段: versionCode");
            if (dto.getEncryptedData() == null || dto.getEncryptedData().isEmpty()) abort("少字段: encryptedData");
            if (dto.getSalt() == null || dto.getSalt().isEmpty()) abort("少字段: salt");
            if (dto.getDeviceFingerprint() == null || dto.getDeviceFingerprint().isEmpty())
                abort("少字段: deviceFingerprint");

            // 时间窗校验
            long serverTime = Instant.now().atOffset(ZoneOffset.ofHours(8)).toEpochSecond();

            long diff = Math.abs(serverTime - dto.getCtime());
            LOG.infof("服务器时间:%s,客户端时间:%s,间隔%s" + serverTime, dto.getCtime(), diff);
            if (diff > securityConfig.getTimeWindowSeconds()) {
                abort("时间不一致:" + diff + "s");
            }

            // 动态密钥
            String dynamicKey = ApiSecurityUtils.generateDynamicKey(
                    securityConfig.getBaseSecretKey(),
                    dto.getSalt(),
                    dto.getDeviceFingerprint(),
                    dto.getCtime()
            );

            // 签名校验
            String expectedSign = ApiSecurityUtils.generateRequestSignature(dynamicKey, path, dto.getEncryptedData(), dto.getCtime());
            if (!expectedSign.equals(dto.getSign())) {
                abort("签名校验失败");
            }

            // 重放校验（异步放行，不阻塞主流程）
            if (securityConfig.isReplayProtectionEnabled()) {
                ReactiveValueCommands<String, String> values = redisDataSource.value(String.class);
                String cacheKey = "api:sign:" + dto.getSign();
                values.get(cacheKey)
                        .onItem().invoke(existing -> {
                            if (existing != null) abort("Duplicate signature detected");
                        })
                        .onItem().transformToUni(x -> values.setex(cacheKey, (long) securityConfig.getSignatureCacheTtl(), "1"))
                        .subscribe().with(
                                ok -> LOG.debug("Replay protection cached sign=" + dto.getSign()),
                                err -> LOG.error("Replay protection cache failed error=" + err.getMessage())
                        );
            }

            // 解密并回写
            String decrypted = ApiSecurityUtils.decrypt(dto.getEncryptedData(), dynamicKey);
            LOG.infof("接口：%s 传递前解密内容：%s", path, decrypted);
            context.setInputStream(new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8)));

            // 保存上下文供响应加密使用
            securityRequestContext.setDynamicKey(dynamicKey);
            securityRequestContext.setSalt(dto.getSalt());
            securityRequestContext.setRequestCtime(dto.getCtime());

            return context.proceed();
        } catch (WebApplicationException ex) {
            throw ex;
        } catch (Exception e) {
            LOG.error("Failed to process encrypted request error=" + e.getMessage());
            abort("Failed to process encrypted request: " + e.getMessage());
            return null; // unreachable
        }
    }

    /**
     * 检查是否为缓存管理路径
     */
    private boolean isCacheManagementPath(String path) {
        if (path == null) {
            return false;
        }

        // 移除开头的斜杠，统一格式
        String normalizedPath = path.startsWith("/") ? path.substring(1) : path;

        // 检查是否以 api/cache-management 开头
        return normalizedPath.startsWith("api/cache-management");
    }

    private void abort(String message) {
        LOG.errorf("请求错误,消息:%s", message);
        ResponseDto<Void> error = ResponseDto.fail(message, 406);
        throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST)
                .type(MediaType.APPLICATION_JSON)
                .entity(error)
                .build());
    }
}

